﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.IO;
using System.IO.Ports;

using Modbus.Data;
using Modbus.Device;
using Modbus.Utility;
using System.Reflection;
using System.Web.Script.Serialization;
using System.Threading;
using System.Runtime.InteropServices;

namespace ModMaster
{
    public partial class frmMain : Form
    {
        string app_name = "";
        app_config config = new app_config();
        SerialPort port;
        IModbusSerialMaster master;
        UInt32 send_count, recv_count, error_count;
        mb_config mbc;

        private Semaphore modbus_sem = new Semaphore(0, 2);
        private bool modbus_thread_stop = false;
        private System.Threading.Thread modbus_thread = null;

        #region 检测USB串口的热拔插
        // usb消息定义
        public const int WM_DEVICE_CHANGE = 0x219;
        public const int DBT_DEVICEARRIVAL = 0x8000;
        public const int DBT_DEVICE_REMOVE_COMPLETE = 0x8004;
        public const UInt32 DBT_DEVTYP_PORT = 0x00000003;
        [StructLayout(LayoutKind.Sequential)]
        struct DEV_BROADCAST_HDR
        {
            public UInt32 dbch_size;
            public UInt32 dbch_devicetype;
            public UInt32 dbch_reserved;
        }

        [StructLayout(LayoutKind.Sequential)]
        protected struct DEV_BROADCAST_PORT_Fixed
        {
            public uint dbcp_size;
            public uint dbcp_devicetype;
            public uint dbcp_reserved;
            // Variable?length field dbcp_name is declared here in the C header file.
        }

        /// <summary>
        /// 检测USB串口的拔插
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            DEV_BROADCAST_HDR dbhdr;
            if (m.Msg == WM_DEVICE_CHANGE)        // 捕获USB设备的拔出消息WM_DEVICECHANGE
            {
                switch (m.WParam.ToInt32())
                {
                case DBT_DEVICE_REMOVE_COMPLETE:    // USB拔出  
                    dbhdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
                    if (dbhdr.dbch_devicetype == DBT_DEVTYP_PORT)
                    {
                        string portName = Marshal.PtrToStringUni((IntPtr)(m.LParam.ToInt32() + Marshal.SizeOf(typeof(DEV_BROADCAST_PORT_Fixed))));
                        Console.WriteLine("Port '" + portName + "' removed.");

                        if (portName.CompareTo(port.PortName) == 0)
                        {
                            serial_port_close();
                        }
                    }
                    break;
                case DBT_DEVICEARRIVAL:             // USB插入获取对应串口名称
                    dbhdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
                    if (dbhdr.dbch_devicetype == DBT_DEVTYP_PORT)
                    {
                        string portName = Marshal.PtrToStringUni((IntPtr)(m.LParam.ToInt32() + Marshal.SizeOf(typeof(DEV_BROADCAST_PORT_Fixed))));
                        Console.WriteLine("Port '" + portName + "' arrived.");
                    }
                    break;
                }
            }
            base.WndProc(ref m);
        }
        #endregion

        public frmMain()
        {
            InitializeComponent();

            app_name = this.Text;
            port = new SerialPort();

            string[] ports = SerialPort.GetPortNames();
            Array.Sort(ports);
            cbo_com.Items.AddRange(ports);
            cbo_com.SelectedIndex = cbo_com.Items.Count > 0 ? 0 : -1;

            string[] baud_rates = { "110", "300", "600", "1200", "2400", "4800", "9600", "14400", "19200", "38400", "56000", "57600", "115200", "128000", "256000" };
            cbo_port_baud_rate.Items.AddRange(baud_rates);
            cbo_port_baud_rate.Text = "9600";

            list_show.DoubleBufferedListView(true);

            try
            {
                string json = File.ReadAllText("config.json", TxtFileEncoder.GetEncoding("config.json"));

                JavaScriptSerializer js = new JavaScriptSerializer();
                config = js.Deserialize<app_config>(json);

                cbo_com.Text = config.port_name;
                num_interval.Value = config.timer_interval;
                num_wait.Value = config.modbus_wait;
                num_timeout.Value = config.modbus_timeout;
                mb_config_load(config.file_name);
            }
            catch
            {
                config.port_name = cbo_com.Text;
                config.file_name = "";
                config.timer_interval = (int)num_interval.Value;
                config.modbus_wait = (int)num_wait.Value;
                config.modbus_timeout = (int)num_timeout.Value;
            }
        }

        private void app_config_save()
        {
            JavaScriptSerializer js = new JavaScriptSerializer();
            string json = js.Serialize(config);

            File.WriteAllText("config.json", json_format(json));
        }

        private void cbo_com_DropDown(object sender, EventArgs e)
        {
            String com_old = "";
            if (cbo_com.SelectedIndex >= 0)
            {
                com_old = cbo_com.Text;
            }

            string[] ports = SerialPort.GetPortNames();
            Array.Sort(ports);
            cbo_com.Items.Clear();
            cbo_com.Items.AddRange(ports);

            if (com_old != "")
            {
                cbo_com.Text = com_old;
            }
            if (cbo_com.SelectedIndex < 0)
            {
                cbo_com.SelectedIndex = cbo_com.Items.Count > 0 ? 0 : -1;
            }
        }

        private void serial_port_close()
        {
            port.Close();
            timer_tick.Enabled = false;
            
            if (modbus_thread != null)
            {
                modbus_thread_stop = true;
                while (modbus_thread != null)
                {
                    modbus_sem_release();
                    Application.DoEvents();
                }
            }
            while (modbus_sem.WaitOne(1)) ;

            btn_open.Text = "打开";
            num_slave_id.Enabled = true;
            cbo_com.Enabled = true;
            cbo_port_baud_rate.Enabled = true;
            num_interval.Enabled = true;
            num_wait.Enabled = true;
            num_timeout.Enabled = true;

            master.Dispose();
        }

        private void serial_port_open()
        {
            // configure serial port
            port.PortName = cbo_com.Text;
            port.BaudRate = int.Parse(cbo_port_baud_rate.Text);
            port.DataBits = 8;
            port.Parity = Parity.None;
            port.StopBits = StopBits.One;

            port.Open();

            btn_open.Text = "关闭";
            num_slave_id.Enabled = false;
            cbo_com.Enabled = false;
            cbo_port_baud_rate.Enabled = false;
            num_interval.Enabled = false;
            num_wait.Enabled = false;
            num_timeout.Enabled = false;
            send_count = 0;
            toolStripStatusLabelSend.Text = "发送:" + send_count.ToString();
            recv_count = 0;
            toolStripStatusLabelRecv.Text = "接收:" + recv_count.ToString();
            error_count = 0;
            toolStripStatusLabelError.Text = "错误:" + error_count.ToString();

            // create modbus master
            master = ModbusSerialMaster.CreateRtu(port);
            master.Transport.ReadTimeout = (int)num_timeout.Value;
            master.Transport.WriteTimeout = (int)num_timeout.Value;
            master.Transport.Retries = 0;
            master.Transport.WaitToRetryMilliseconds = 0;

            modbus_thread_stop = false;
            modbus_thread = new System.Threading.Thread(modbus_thread_function);
            modbus_thread.Start();
            timer_tick.Interval = (int)num_interval.Value;
            timer_tick.Enabled = true;
        }

        private void btn_open_Click(object sender, EventArgs e)
        {
            try
            {
                if (port.IsOpen)
                {
                    serial_port_close();
                }
                else
                {
                    serial_port_open();
                }
            }
            catch (Exception ex)
            {
                port = new SerialPort();

                btn_open.Text = "打开";
                cbo_com.Enabled = true;
                cbo_port_baud_rate.Enabled = true;
                num_interval.Enabled = true;
                num_wait.Enabled = true;
                num_timeout.Enabled = true;
                MessageBoxEx.Show(ex.Message, "消息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        public static byte order_parse(string order)
        {
            if (order == null)
            {
                return 0x02; // BADC
            }
            if (order == "ABCD")
            {
                return 0x00;
            }
            if (order == "CDAB")
            {
                return 0x01;
            }
            if (order == "BADC")
            {
                return 0x02;
            }
            if (order == "DCBA")
            {
                return 0x03;
            }

            return 0x02; // BADC
        }

        byte[] registers_to_bytes(ushort[] registers, int offset, int length, byte order)
        {
            List<byte> bytes = new List<byte>();

            for (int i = 0; i < length; i++)
            {
                byte[] bytes_tmp;

                if ((order & 0x01) == 0)
                {
                    bytes_tmp = BitConverter.GetBytes(registers[offset + i]);
                }
                else
                {
                    bytes_tmp = BitConverter.GetBytes(registers[offset + length - 1 - i]);
                }

                if ((order & 0x02) == 0)
                {
                    Array.Reverse(bytes_tmp);
                }

                bytes.AddRange(bytes_tmp);
            }

            return bytes.ToArray();
        }

        public static ushort[] bytes_to_registers(byte[] bytes, int offset, int length, byte order)
        {
            List<ushort> registers = new List<ushort>();
            int reg_num = length / 2;

            for (int i = 0; i < reg_num; i++)
            {
                byte[] bytes_tmp = new byte[2];

                if ((order & 0x01) == 0)
                {
                    Array.Copy(bytes, offset + i * 2, bytes_tmp, 0, 2);
                }
                else
                {
                    Array.Copy(bytes, offset + (reg_num - 1 - i) * 2, bytes_tmp, 0, 2);
                }

                if ((order & 0x02) == 0)
                {
                    Array.Reverse(bytes_tmp);
                }

                registers.Add(BitConverter.ToUInt16(bytes_tmp, 0));
            }

            return registers.ToArray();
        }

        public static ushort[] bytes_to_registers(byte[] bytes, byte order)
        {
            return bytes_to_registers(bytes, 0, bytes.Length, order);
        }

        private void master_read_wait()
        {
            int wait = (int)num_wait.Value;
            while(wait >= 10)
            {
                if (modbus_thread_stop)
                {
                    return;
                }
                Thread.Sleep(10);
                wait -= 10;
            }
            if (modbus_thread_stop)
            {
                return;
            }
            Thread.Sleep(wait);
        }

        void read_coil_status_ex(int lines, int start, int last, int address, int number)
        {
            try
            {
                master_read_wait();
                if (modbus_thread_stop)
                {
                    return;
                }

                send_count++;
                this.BeginInvoke((EventHandler)(delegate
                {
                    toolStripStatusLabelSend.Text = "发送:" + send_count.ToString();
                }));

                bool[] status = master.ReadCoils((byte)num_slave_id.Value, (UInt16)address, (UInt16)number);
                for (int j = start; j < last; j++)
                {
                    int index_tmp = lines + j;
                    string str_tmp = mb_values.find(mbc.coil_status[j].values, status[j - start].ToString());
                    this.BeginInvoke((EventHandler)(delegate
                    {
                        list_show.Items[index_tmp].BackColor = Color.White;
                        list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                    }));
                }

                recv_count++;
                this.BeginInvoke((EventHandler)(delegate
                {
                    toolStripStatusLabelRecv.Text = "接收:" + recv_count.ToString();
                }));
            }
            catch
            {
                for (int j = start; j < last; j++)
                {
                    int index_tmp = lines + j;
                    this.BeginInvoke((EventHandler)(delegate
                    {
                        list_show.Items[index_tmp].BackColor = Color.Tomato;
                    }));
                }

                error_count++;
                toolStripStatusLabelError.Text = "错误:" + error_count.ToString();
            }
        }

        void read_coil_status(int lines)
        {
            int i, k = 0;
            int address = 0, number = 0;
            for (i = 0; i < mbc.coil_status.Length; i++)
            {
                if (number == 0)
                {
                    k = i;
                    address = mbc.coil_status[i].address;
                    number = 1;
                }
                else if ((number < 255) && (mbc.coil_status[i].address == address + number))
                {
                    number++;
                }
                else
                {
                    read_coil_status_ex(lines, k, i, address, number);
                    if (modbus_thread_stop)
                    {
                        break;
                    }

                    k = i;
                    address = mbc.coil_status[i].address;
                    number = 1;
                }
            }
            if (number > 0)
            {
                read_coil_status_ex(lines, k, i, address, number);
            }
        }

        void read_input_status_ex(int lines, int start, int last, int address, int number)
        {
            try
            {
                master_read_wait();
                if (modbus_thread_stop)
                {
                    return;
                }

                send_count++;
                this.BeginInvoke((EventHandler)(delegate
                {
                    toolStripStatusLabelSend.Text = "发送:" + send_count.ToString();
                }));

                bool[] status = master.ReadInputs((byte)num_slave_id.Value, (UInt16)address, (UInt16)number);
                for (int j = start; j < last; j++)
                {
                    int index_tmp = lines + j;
                    string str_tmp = mb_values.find(mbc.input_status[j].values, status[j - start].ToString());
                    this.BeginInvoke((EventHandler)(delegate
                    {
                        list_show.Items[index_tmp].BackColor = Color.White;
                        list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                    }));
                }

                recv_count++;
                this.BeginInvoke((EventHandler)(delegate
                {
                    toolStripStatusLabelRecv.Text = "接收:" + recv_count.ToString();
                }));
            }
            catch
            {
                for (int j = start; j < last; j++)
                {
                    int index_tmp = lines + j;
                    this.BeginInvoke((EventHandler)(delegate
                    {
                        list_show.Items[index_tmp].BackColor = Color.Tomato;
                    }));
                }

                error_count++;
                toolStripStatusLabelError.Text = "错误:" + error_count.ToString();
            }
        }

        void read_input_status(int lines)
        {
            int i, k = 0;
            int address = 0, number = 0;
            for (i = 0; i < mbc.input_status.Length; i++)
            {
                if (number == 0)
                {
                    k = i;
                    address = mbc.input_status[i].address;
                    number++;
                }
                else if ((number < 255) && (mbc.input_status[i].address == address + number))
                {
                    number++;
                }
                else
                {
                    read_input_status_ex(lines, k, i, address, number);
                    if (modbus_thread_stop)
                    {
                        break;
                    }

                    k = i;
                    address = mbc.input_status[i].address;
                    number = 1;
                }
            }
            if (number > 0)
            {
                read_input_status_ex(lines, k, i, address, number);
            }
        }

        public static string bits_to_string(byte[] bytes)
        {
            string str = "";
            int k = 0;
            for (int i = 0; i < bytes.Length; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    if ((k != 0) && ((k % 4) == 0))
                    {
                        str += ",";
                    }
                    k++;
                    str += (((bytes[bytes.Length - 1 - i] & (0x80 >> j)) != 0) ? "1" : "0");
                }
            }

            return str;
        }

        public static byte[] string_to_bits(string str)
        {
            List<byte> bytes = new List<byte>();
            string str_tmp = str.Replace(",", "");
            byte n = 0;
            int k = str_tmp.Length % 16;
            if (k > 0)
            {
                str_tmp = new string('0', 16 - k) + str_tmp;
            }
            for (int i = 0; i < str_tmp.Length; i++)
            {
                n <<= 1;
                if (str_tmp[i] != '0')
                {
                    n |= 1;
                }
                if ((i % 8) == 7)
                {
                    bytes.Insert(0, n);
                }
            }

            return bytes.ToArray();
        }

        public static string hex_to_string(byte[] bytes)
        {
            string str = "0x";
            for (int i = 0; i < bytes.Length; i++)
            {
                str += bytes[bytes.Length - 1 - i].ToString("X2");
            }

            return str;
        }

        public static byte[] string_to_bytes(string str)
        {
            List<byte> bytes = new List<byte>();
            string str_tmp = str.ToLower().Replace("0x", "");
            byte n = 0;
            if ((str_tmp.Length % 2) != 0)
            {
                str_tmp = "0" + str_tmp;
            }
            for (int i = 0; i < str_tmp.Length; i += 2)
            {
                n = 0;
                Byte.TryParse(str_tmp.Substring(i, 2), System.Globalization.NumberStyles.HexNumber, null, out n);
                bytes.Insert(0, n);
            }

            return bytes.ToArray();
        }

        void read_holding_register_ex(int lines, int start, int last, int address, int number)
        {
            try
            {
                master_read_wait();
                if (modbus_thread_stop)
                {
                    return;
                }

                send_count++;
                this.BeginInvoke((EventHandler)(delegate
                {
                    toolStripStatusLabelSend.Text = "发送:" + send_count.ToString();
                }));

                UInt16[] registers = master.ReadHoldingRegisters((byte)num_slave_id.Value, (UInt16)address, (UInt16)number);

                int offset = 0;
                for (int j = start; j < last; j++)
                {
                    string value_type = mbc.holding_register[j].value_type.ToLower();
                    int length = mbc.holding_register[j].length;
                    byte order = order_parse(mbc.holding_register[j].order.ToUpper());
                    double scale = mbc.holding_register[j].scale;
                    string fmt = mbc.holding_register[j].format;
                    if (scale == 0)
                    {
                        scale = 1;
                    }
                    if (length < 1)
                    {
                        length = 1;
                    }
                    if (value_type == "uint16_t")
                    {
                        UInt16 value = BitConverter.ToUInt16(registers_to_bytes(registers, offset, 1, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 1;
                    }
                    else if (value_type == "uint32_t")
                    {
                        UInt32 value = BitConverter.ToUInt32(registers_to_bytes(registers, offset, 2, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 2;
                    }
                    else if (value_type == "uint64_t")
                    {
                        UInt64 value = BitConverter.ToUInt64(registers_to_bytes(registers, offset, 4, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 4;
                    }
                    else if (value_type == "int16_t")
                    {
                        Int16 value = BitConverter.ToInt16(registers_to_bytes(registers, offset, 1, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 1;
                    }
                    else if (value_type == "int32_t")
                    {
                        Int32 value = BitConverter.ToInt32(registers_to_bytes(registers, offset, 2, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 2;
                    }
                    else if (value_type == "int64_t")
                    {
                        Int64 value = BitConverter.ToInt64(registers_to_bytes(registers, offset, 4, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 4;
                    }
                    else if (value_type == "bits")
                    {
                        byte[] bytes = registers_to_bytes(registers, offset, length, order);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, bits_to_string(bytes));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += length;
                    }
                    else if (value_type == "hex")
                    {
                        byte[] bytes = registers_to_bytes(registers, offset, length, order);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, hex_to_string(bytes));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += length;
                    }
                    else if (value_type == "float")
                    {
                        Single value = BitConverter.ToSingle(registers_to_bytes(registers, offset, 2, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 2;
                    }
                    else if (value_type == "double")
                    {
                        Double value = BitConverter.ToDouble(registers_to_bytes(registers, offset, 4, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 4;
                    }
                    else
                    {
                        byte[] bytes = registers_to_bytes(registers, offset, length, order);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.holding_register[j].values, hex_to_string(bytes));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += length;
                    }
                }

                recv_count++;
                this.BeginInvoke((EventHandler)(delegate
                {
                    toolStripStatusLabelRecv.Text = "接收:" + recv_count.ToString();
                }));
            }
            catch
            {
                for (int j = start; j < last; j++)
                {
                    int index_tmp = lines + j;
                    this.BeginInvoke((EventHandler)(delegate
                    {
                        list_show.Items[index_tmp].BackColor = Color.Tomato;
                    }));
                }

                error_count++;
                toolStripStatusLabelError.Text = "错误:" + error_count.ToString();
            }
        }

        void read_holding_register(int lines)
        {
            int i, k = 0;
            int address = 0, number = 0;
            for (i = 0; i < mbc.holding_register.Length; i++)
            {
                if (number == 0)
                {
                    k = i;
                    address = mbc.holding_register[i].address;
                    number = mbc.holding_register[i].length;
                }
                else if ((number < 255) && (mbc.holding_register[i].address == address + number))
                {
                    number += mbc.holding_register[i].length;
                }
                else
                {
                    read_holding_register_ex(lines, k, i, address, number);
                    if (modbus_thread_stop)
                    {
                        break;
                    }

                    k = i;
                    address = mbc.holding_register[i].address;
                    number = mbc.holding_register[i].length;
                }
            }
            if (number > 0)
            {
                read_holding_register_ex(lines, k, i, address, number);
            }
        }

        void read_input_register_ex(int lines, int start, int last, int address, int number)
        {
            try
            {
                master_read_wait();
                if (modbus_thread_stop)
                {
                    return;
                }

                send_count++;
                this.BeginInvoke((EventHandler)(delegate
                {
                    toolStripStatusLabelSend.Text = "发送:" + send_count.ToString();
                }));

                UInt16[] registers = master.ReadInputRegisters((byte)num_slave_id.Value, (UInt16)address, (UInt16)number);

                int offset = 0;
                for (int j = start; j < last; j++)
                {
                    string value_type = mbc.input_register[j].value_type.ToLower();
                    int length = mbc.input_register[j].length;
                    byte order = order_parse(mbc.input_register[j].order.ToUpper());
                    double scale = mbc.input_register[j].scale;
                    string fmt = mbc.input_register[j].format;
                    if (scale == 0)
                    {
                        scale = 1;
                    }
                    if (length < 1)
                    {
                        length = 1;
                    }
                    if (value_type == "uint16_t")
                    {
                        UInt16 value = BitConverter.ToUInt16(registers_to_bytes(registers, offset, 1, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 1;
                    }
                    else if (value_type == "uint32_t")
                    {
                        UInt32 value = BitConverter.ToUInt32(registers_to_bytes(registers, offset, 2, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 2;
                    }
                    else if (value_type == "uint64_t")
                    {
                        UInt64 value = BitConverter.ToUInt64(registers_to_bytes(registers, offset, 4, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 4;
                    }
                    else if (value_type == "int16_t")
                    {
                        Int16 value = BitConverter.ToInt16(registers_to_bytes(registers, offset, 1, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 1;
                    }
                    else if (value_type == "int32_t")
                    {
                        Int32 value = BitConverter.ToInt32(registers_to_bytes(registers, offset, 2, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 2;
                    }
                    else if (value_type == "int64_t")
                    {
                        Int64 value = BitConverter.ToInt64(registers_to_bytes(registers, offset, 4, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 4;
                    }
                    else if (value_type == "bits")
                    {
                        byte[] bytes = registers_to_bytes(registers, offset, length, order);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, bits_to_string(bytes));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += length;
                    }
                    else if (value_type == "hex")
                    {
                        byte[] bytes = registers_to_bytes(registers, offset, length, order);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, hex_to_string(bytes));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += length;
                    }
                    else if (value_type == "float")
                    {
                        Single value = BitConverter.ToSingle(registers_to_bytes(registers, offset, 2, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 2;
                    }
                    else if (value_type == "double")
                    {
                        Double value = BitConverter.ToDouble(registers_to_bytes(registers, offset, 4, order), 0);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, (value * scale).ToString(fmt));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += 4;
                    }
                    else
                    {
                        byte[] bytes = registers_to_bytes(registers, offset, length, order);
                        int index_tmp = lines + j;
                        string str_tmp = mb_values.find(mbc.input_register[j].values, hex_to_string(bytes));
                        this.BeginInvoke((EventHandler)(delegate
                        {
                            list_show.Items[index_tmp].BackColor = Color.White;
                            list_show.Items[index_tmp].SubItems[5].Text = str_tmp;
                        }));
                        offset += length;
                    }
                }

                recv_count++;
                this.BeginInvoke((EventHandler)(delegate
                {
                    toolStripStatusLabelRecv.Text = "接收:" + recv_count.ToString();
                }));
            }
            catch
            {
                for (int j = start; j < last; j++)
                {
                    int index_tmp = lines + j;
                    this.BeginInvoke((EventHandler)(delegate
                    {
                        list_show.Items[index_tmp].BackColor = Color.Tomato;
                    }));
                }

                error_count++;
                toolStripStatusLabelError.Text = "错误:" + error_count.ToString();
            }
        }

        void read_input_register(int lines)
        {
            int i, k = 0;
            int address = 0, number = 0;
            for (i = 0; i < mbc.input_register.Length; i++)
            {
                if (number == 0)
                {
                    k = i;
                    address = mbc.input_register[i].address;
                    number = mbc.input_register[i].length;
                }
                else if ((number < 255) && (mbc.input_register[i].address == address + number))
                {
                    number += mbc.input_register[i].length;
                }
                else
                {
                    read_input_register_ex(lines, k, i, address, number);
                    if (modbus_thread_stop)
                    {
                        break;
                    }

                    k = i;
                    address = mbc.input_register[i].address;
                    number = mbc.input_register[i].length;
                }
            }
            if (number > 0)
            {
                read_input_register_ex(lines, k, i, address, number);
            }
        }

        private void modbus_sem_release()
        {
            try
            {
                modbus_sem.Release();
            }
            catch
            {

            }
        }

        private void modbus_thread_function()
        {
            try
            {
                while (true)
                {
                    modbus_sem.WaitOne();
                    if (modbus_thread_stop)
                    {
                        break;
                    }
                    int lines = 0;
                    read_coil_status(lines);
                    lines += mbc.coil_status.Length;
                    if (modbus_thread_stop)
                    {
                        break;
                    }
                    read_input_status(lines);
                    lines += mbc.input_status.Length;
                    if (modbus_thread_stop)
                    {
                        break;
                    }
                    read_holding_register(lines);
                    lines += mbc.holding_register.Length;
                    if (modbus_thread_stop)
                    {
                        break;
                    }
                    read_input_register(lines);
                    lines += mbc.input_register.Length;
                    if (modbus_thread_stop)
                    {
                        break;
                    }
                }
            }
            finally
            {
                modbus_thread = null;
            }
        }

        private void timer_tick_Tick(object sender, EventArgs e)
        {
            modbus_sem_release();
        }

        private void List_show_Resize(object sender, EventArgs e)
        {
            list_show.Columns[list_show.Columns.Count - 1].Width = -2;
            list_show.Columns[list_show.Columns.Count - 1].Width -= 1;
        }

        private string json_format(string json)
        {
            int indent = 0;
            bool esc_flag = false;
            bool str_flag = false;

            for (int i = 0; i < json.Length; i++)
            {
                if (esc_flag)
                {
                    esc_flag = false;
                }
                else if (json[i] == '\\')
                {
                    esc_flag = true;
                }
                else if (json[i] == '\"')
                {
                    str_flag = !str_flag;
                }
                else if (str_flag)
                {

                }
                else if ((json[i] == '{') || (json[i] == '['))
                {
                    indent++;
                    string str_tmp = "\r\n" + new string(' ', indent * 4);
                    json = json.Insert(i + 1, str_tmp);
                    i += str_tmp.Length;
                }
                else if ((json[i] == '}') || (json[i] == ']'))
                {
                    indent--;
                    string str_tmp = "\r\n" + new string(' ', indent * 4);
                    json = json.Insert(i, str_tmp);
                    i += str_tmp.Length;
                }
                else if ((json[i] == ',') && (json[i + 1] != '{') && (json[i + 1] != '['))
                {
                    string str_tmp = "\r\n" + new string(' ', indent * 4);
                    json = json.Insert(i + 1, str_tmp);
                    i += str_tmp.Length;
                }
            }

            return json;
        }

        //private void Lbl_config_export_Click(object sender, EventArgs e)
        //{
        //    SaveFileDialog fileDialog = new SaveFileDialog();

        //    fileDialog.Title = "导出";
        //    fileDialog.Filter = "JSON文件(*.json)|*.json|所有文件(*.*)|*.*";
        //    if (fileDialog.ShowDialog() == DialogResult.OK)
        //    {
        //        mbc.baud_rate = UInt32.Parse(cbo_port_baud_rate.Text);
        //        mbc.device_id = (byte)num_slave_id.Value;

        //        JavaScriptSerializer js = new JavaScriptSerializer();
        //        string json = js.Serialize(mbc);

        //        File.WriteAllText(fileDialog.FileName, json_format(json));
        //    }
        //}

        private void mb_data_send(ListViewItem item)
        {
            int type = -1;
            mb_data data = null;
            string value = item.SubItems[5].Text;
            int index = int.Parse(item.SubItems[0].Text) - 1;
            if (index < mbc.coil_status.Length)
            {
                type = 0;
                data = mbc.coil_status[index];
            }
            if (type < 0)
            {
                index -= mbc.coil_status.Length;
                if (index < mbc.input_status.Length)
                {
                    type = 1;
                    data = mbc.input_status[index];
                }
            }
            if (type < 0)
            {
                index -= mbc.input_status.Length;
                if (index < mbc.holding_register.Length)
                {
                    type = 2;
                    data = mbc.holding_register[index];
                }
            }
            if (type < 0)
            {
                index -= mbc.holding_register.Length;
                if (index < mbc.input_register.Length)
                {
                    type = 3;
                    data = mbc.input_register[index];
                }
            }
            if (!data.read_only && ((type == 0) || (type == 2)))
            {
                frmSend dlg = new frmSend(master, (byte)num_slave_id.Value, data, type, value);
                dlg.ShowDialog();
            }
        }

        private void List_show_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if ((port.IsOpen) && (list_show.SelectedItems.Count > 0))
            {
                ListViewHitTestInfo lvi = list_show.HitTest(e.Location);
                if ((lvi != null) && (lvi.Item == list_show.SelectedItems[0]))
                {
                    mb_data_send(list_show.SelectedItems[0]);
                }
            }
        }

        private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            config.port_name = cbo_com.Text;
            config.timer_interval = (int)num_interval.Value;
            config.modbus_wait = (int)num_wait.Value;
            config.modbus_timeout = (int)num_timeout.Value;
            app_config_save();

            if(port.IsOpen)
            {
                serial_port_close();
            }
        }

        private mb_data[] mb_config_check(mb_data[] datas)
        {
            if(datas == null)
            {
                return new mb_data[0];
            }

            for (int i = 0; i < datas.Length; i++)
            {
                // string name;
                // int address;
                // int length;
                // string value_type;
                // string order;
                // double scale;
                // string format;
                // mb_values[] values;
                // bool read_only;
                if (datas[i].length <= 0)
                {
                    if (datas[i].value_type == "uint16_t")
                    {
                        datas[i].length = 1;
                    }
                    else if (datas[i].value_type == "uint32_t")
                    {
                        datas[i].length = 2;
                    }
                    else if (datas[i].value_type == "uint64_t")
                    {
                        datas[i].length = 4;
                    }
                    else if (datas[i].value_type == "int16_t")
                    {
                        datas[i].length = 1;
                    }
                    else if (datas[i].value_type == "int32_t")
                    {
                        datas[i].length = 2;
                    }
                    else if (datas[i].value_type == "int64_t")
                    {
                        datas[i].length = 4;
                    }
                    else if (datas[i].value_type == "bits")
                    {
                        datas[i].length = 1;
                    }
                    else if (datas[i].value_type == "hex")
                    {
                        datas[i].length = 1;
                    }
                    else if (datas[i].value_type == "float")
                    {
                        datas[i].length = 2;
                    }
                    else if (datas[i].value_type == "double")
                    {
                        datas[i].length = 4;
                    }
                    else
                    {
                        datas[i].value_type = "hex";
                        datas[i].length = 1;
                    }
                }
                if ((i > 0) && (datas[i].address < 0))
                {
                    datas[i].address = datas[i - 1].address + datas[i - 1].length;
                }
                if (datas[i].scale == 0)
                {
                    datas[i].scale = 1;
                }
                if (datas[i].format == null)
                {
                    datas[i].format = "";
                }
                if (datas[i].order == null)
                {
                    datas[i].order = "BADC";
                }
                else
                {
                    string str_order = datas[i].order.ToUpper();
                    if ((str_order != "ABCD") && (str_order != "CDAB") && (str_order != "DCBA") && (str_order != "BADC"))
                    {
                        datas[i].order = "BADC";
                    }
                }
            }

            return datas;
        }

        private void mb_config_load(string file_name)
        {
            string json = File.ReadAllText(file_name, TxtFileEncoder.GetEncoding(file_name));
            JavaScriptSerializer js = new JavaScriptSerializer();
            mbc = js.Deserialize<mb_config>(json);
            mbc.coil_status = mb_config_check(mbc.coil_status);
            mbc.input_status = mb_config_check(mbc.input_status);
            mbc.holding_register = mb_config_check(mbc.holding_register);
            mbc.input_register = mb_config_check(mbc.input_register);

            config.file_name = file_name;
            this.Text = app_name + " - " + file_name.Substring(file_name.LastIndexOf("\\") + 1);
            cbo_port_baud_rate.Text = mbc.baud_rate.ToString();
            num_slave_id.Value = mbc.device_id;
            list_show.BeginUpdate();
            list_show.Items.Clear();
            int index = 0;
            for (int i = 0; i < mbc.coil_status.Length; i++)
            {
                index++;
                ListViewItem lvi = new ListViewItem(index.ToString());
                lvi.SubItems.Add("1");
                lvi.SubItems.Add(mbc.coil_status[i].address.ToString());
                lvi.SubItems.Add(mbc.coil_status[i].read_only ? "R" : "RW");
                lvi.SubItems.Add(mbc.coil_status[i].name);
                lvi.SubItems.Add("");
                list_show.Items.Add(lvi);
            }
            for (int i = 0; i < mbc.input_status.Length; i++)
            {
                index++;
                ListViewItem lvi = new ListViewItem(index.ToString());
                lvi.SubItems.Add("2");
                lvi.SubItems.Add(mbc.input_status[i].address.ToString());
                lvi.SubItems.Add("R");
                lvi.SubItems.Add(mbc.input_status[i].name);
                lvi.SubItems.Add("");
                list_show.Items.Add(lvi);
            }
            for (int i = 0; i < mbc.holding_register.Length; i++)
            {
                index++;
                ListViewItem lvi = new ListViewItem(index.ToString());
                lvi.SubItems.Add("3");
                lvi.SubItems.Add(mbc.holding_register[i].address.ToString());
                lvi.SubItems.Add(mbc.holding_register[i].read_only ? "R" : "RW");
                lvi.SubItems.Add(mbc.holding_register[i].name);
                lvi.SubItems.Add("");
                list_show.Items.Add(lvi);
            }
            for (int i = 0; i < mbc.input_register.Length; i++)
            {
                index++;
                ListViewItem lvi = new ListViewItem(index.ToString());
                lvi.SubItems.Add("4");
                lvi.SubItems.Add(mbc.input_register[i].address.ToString());
                lvi.SubItems.Add("R");
                lvi.SubItems.Add(mbc.input_register[i].name);
                lvi.SubItems.Add("");
                list_show.Items.Add(lvi);
            }
            list_show.EndUpdate();
        }

        private void Slab_info_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start("http://www.mysoow.com");  // 在浏览器中打开链接
        }

        private void List_show_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == System.Convert.ToChar(13))
            {
                e.Handled = true;
                for (int i = 0; i < list_show.SelectedItems.Count; i++)
                {
                    mb_data_send(list_show.SelectedItems[i]);
                }
            }
        }

        private void Lbl_config_import_Click(object sender, EventArgs e)
        {
            OpenFileDialog fileDialog = new OpenFileDialog();

            try
            {
                fileDialog.Title = "导入";
                fileDialog.Filter = "JSON文件(*.json)|*.json|所有文件(*.*)|*.*";
                if (fileDialog.ShowDialog() == DialogResult.OK)
                {
                    mb_config_load(fileDialog.FileName);
                }
            }
            catch (Exception ex)
            {
                MessageBoxEx.Show(this, ex.Message, "消息", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }
    }

    public static class DoubleBufferDataGridView
    {
        /// <summary>  
        /// 双缓冲，解决闪烁问题  
        /// </summary>  
        /// <param name="dgv"></param>  
        /// <param name="flag"></param>  
        public static void DoubleBufferedDataGirdView(this DataGridView dgv, bool flag)
        {
            Type dgvType = dgv.GetType();
            PropertyInfo pi = dgvType.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
            pi.SetValue(dgv, flag, null);
        }
    }

    public static class DoubleBufferListView
    {
        /// <summary>  
        /// 双缓冲，解决闪烁问题  
        /// </summary>  
        /// <param name="lv"></param>  
        /// <param name="flag"></param>  
        public static void DoubleBufferedListView(this ListView lv, bool flag)
        {
            Type lvType = lv.GetType();
            PropertyInfo pi = lvType.GetProperty("DoubleBuffered",
                BindingFlags.Instance | BindingFlags.NonPublic);
            pi.SetValue(lv, flag, null);
        }
    }

    public class mb_values
    {
        public string key;
        public string value;

        public static string find(mb_values[] values, string key)
        {
            if ((values == null) || (values.Length == 0))
            {
                return key;
            }

            for (int i = 0; i < values.Length; i++)
            {
                if (values[i].key.ToLower() == key.ToLower())
                {
                    return values[i].value;
                }
            }

            return key;
        }
    }

    public class app_config
    {
        public string port_name;
        public string file_name;
        public int timer_interval;
        public int modbus_wait;
        public int modbus_timeout;
    }

    public class mb_data
    {
        public string name;
        public int address;
        public int length;
        public string value_type;
        public string order;
        public double scale;
        public string format;
        public mb_values[] values;
        public bool read_only;
    }

    public class mb_config
    {
        public UInt32 baud_rate;
        public byte device_id;
        public mb_data[] coil_status;
        public mb_data[] input_status;
        public mb_data[] holding_register;
        public mb_data[] input_register;
    }

    public class TxtFileEncoder
    {
        /// <summary>
        /// 给定文件的路径，读取文件的二进制数据，判断文件的编码类型
        /// </summary>
        /// <param name="FILE_NAME">文件路径</param>
        /// <returns>文件的编码类型</returns>
        public static System.Text.Encoding GetEncoding(string FILE_NAME)
        {
            FileStream fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read);
            Encoding r = GetEncoding(fs);
            fs.Close();
            return r;
        }

        /// <summary>
        /// 通过给定的文件流，判断文件的编码类型
        /// </summary>
        /// <param name="fs">文件流</param>
        /// <returns>文件的编码类型</returns>
        public static System.Text.Encoding GetEncoding(FileStream fs)
        {
            byte[] Unicode = new byte[] { 0xFF, 0xFE, 0x41 };
            byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF, 0x00 };
            byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF }; //带BOM
            Encoding reVal = Encoding.Default;
            BinaryReader r = new BinaryReader(fs, System.Text.Encoding.Default);
            int i;
            int.TryParse(fs.Length.ToString(), out i);
            byte[] ss = r.ReadBytes(i);
            if (IsUTF8Bytes(ss) || (ss[0] == 0xEF && ss[1] == 0xBB && ss[2] == 0xBF))
            {
                reVal = Encoding.UTF8;
            }
            else if (ss[0] == 0xFE && ss[1] == 0xFF && ss[2] == 0x00)
            {
                reVal = Encoding.BigEndianUnicode;
            }
            else if (ss[0] == 0xFF && ss[1] == 0xFE && ss[2] == 0x41)
            {
                reVal = Encoding.Unicode;
            }
            r.Close();
            return reVal;
        }

        /// <summary>
        /// 判断是否是不带 BOM 的 UTF8 格式
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private static bool IsUTF8Bytes(byte[] data)
        {
            int charByteCounter = 1;　 //计算当前正分析的字符应还有的字节数
            byte curByte; //当前分析的字节.
            for (int i = 0; i < data.Length; i++)
            {
                curByte = data[i];
                if (charByteCounter == 1)
                {
                    if (curByte >= 0x80)
                    {
                        //判断当前
                        while (((curByte <<= 1) & 0x80) != 0)
                        {
                            charByteCounter++;
                        }
                        //标记位首位若为非0 则至少以2个1开始 如:110XXXXX...........1111110X　
                        if (charByteCounter == 1 || charByteCounter > 6)
                        {
                            return false;
                        }
                    }
                }
                else
                {
                    //若是UTF-8 此时第一位必须为1
                    if ((curByte & 0xC0) != 0x80)
                    {
                        return false;
                    }
                    charByteCounter--;
                }
            }

            if (charByteCounter > 1)
            {
                throw new Exception("非预期的byte格式");
            }
            return true;
        }
    }
}
